﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace EDTS.Weixin
{
    public class MessageFactory
    {
        /// <summary>
        /// 根据接收到的xml数据生产类实例
        /// </summary>
        /// <param name="doc"></param>
        /// <returns></returns>
        public static Requests.IRequest GetRequest(XDocument doc)
        {
            var msgType = GetMsgType(doc);
            Requests.Request req = null;
            switch (msgType)
            {
                case MsgType.Text:
                    req = new Requests.Text();
                    break;
                case MsgType.Image:
                    req = new Requests.Image();
                    break;
                case MsgType.Link:
                    req = new Requests.Link();
                    break;
                case MsgType.Location:
                    req = new Requests.Location();
                    break;
                case MsgType.Event:
                    req = new Requests.EventMsg();
                    break;
                case MsgType.Voice:
                    req = new Requests.Voice();
                    break;
            }
            if (req != null)
                FillModelWithXml(req, doc);
            else req = new Requests.Request();
            return req;
        }
        /// <summary>
        ///  将需发送的数据封装成xml格式
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <returns></returns>
        //public static XDocument CreateXmlMessage<T>(T obj) where T : class
        //{
        //    XDocument doc = new XDocument();
        //    doc.Add(new XElement("xml"));
        //    var root = doc.Root;

        //    #region 字段排序（经过测试，微信对字段排序有严格要求，这里对排序进行强制约束）
        //    var propNameOrder = new[] { "ToUserName", "FromUserName", "CreateTime", "MsgType", "Content", "ArticleCount", "Articles", "Music", "FuncFlag",/*以下是Atricle属性*/"Title ", "Description ", "PicUrl", "Url" }.ToList();
        //    Func<string, int> orderByPropName = propNameOrder.IndexOf;
        //    var props = obj.GetType().GetProperties().OrderBy(p => orderByPropName(p.Name)).ToList();
        //    #endregion

        //    foreach (var p in props)
        //    {
        //        var pn = p.Name;
        //        if (pn == "Articles")
        //        {
        //            var articlesElement = new XElement("Articles");
        //            var articles = p.GetValue(obj, null) as List<Response.News.Item>;
        //            foreach (var it in articles)
        //            {
        //                var subNodes = CreateXmlMessage(it).Root.Elements();
        //                articlesElement.Add(new XElement("item", subNodes));
        //            }
        //            root.Add(articlesElement);
        //        }
        //        else if (pn == "Music")
        //        {
        //            var music = p.GetValue(obj, null) as Response.Sound.Item;
        //            var subNodes = CreateXmlMessage(music).Root.Elements();
        //            root.Add(new XElement("Music", subNodes));
        //        }
        //        else
        //        {
        //            switch (p.PropertyType.Name)
        //            {
        //                case "String":
        //                    root.Add(new XElement(pn, new XCData(p.GetValue(obj, null) as string ?? "")));
        //                    break;
        //                case "DateTime":
        //                    root.Add(new XElement(pn, ConvertToLong((DateTime)p.GetValue(obj, null))));
        //                    break;
        //                case "Boolean":
        //                    if (pn == "FuncFlag")
        //                        root.Add(new XElement(pn, (bool)p.GetValue(obj, null) ? "1" : "0"));
        //                    else
        //                        goto default;
        //                    break;
        //                case "MsgType":
        //                    root.Add(new XElement(pn, new XCData(p.GetValue(obj, null).ToString().ToLower())));
        //                    break;
        //                case "Article":
        //                    root.Add(new XElement(pn, p.GetValue(obj, null).ToString().ToLower()));
        //                    break;
        //                default:
        //                    root.Add(new XElement(pn, p.GetValue(obj, null)));
        //                    break;
        //            }
        //        }
        //    }
        //    return doc;
        //}

        /// <summary>
        /// 取得消息类型
        /// </summary>
        /// <param name="doc"></param>
        /// <returns></returns>
        static MsgType GetMsgType(XDocument doc)
        {
            var str = doc.Root.Element("MsgType").Value;
            return GetMsgType(str);
        }
        static MsgType GetMsgType(string name)
        {
            return (MsgType)Enum.Parse(typeof(MsgType), name, true);
        }
        /// <summary>
        /// 取得事件类型
        /// </summary>
        /// <param name="doc"></param>
        /// <returns></returns>
        static EventType GetEventType(XDocument doc)
        {
            var str = doc.Root.Element("EventType").Value;
            return GetEventType(str);
        }
        static EventType GetEventType(string name)
        {
            return (EventType)Enum.Parse(typeof(EventType), name, true);
        }

        /// <summary>
        /// 时间格式转换（将接收到的秒数转为datetime)
        /// </summary>
        /// <param name="createtime"></param>
        /// <returns></returns>
        static DateTime ConvertToDateTime(string createtime)
        {
            var basetime = DateTime.Parse("1970-1-1 0:0:0");
            var ct = long.Parse(createtime) * 10000000;
            return new DateTime(ct + basetime.Ticks).AddHours(8);
        }
        /// <summary>
        /// 时间格式转换（将系统时间转换为微信服务器定义的秒数UTC)
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        static long ConvertToLong(DateTime dt)
        {
            var basetime = DateTime.Parse("1970-1-1 0:0:0");
            return (dt.AddHours(-8).Ticks - basetime.Ticks) / 10000000;
        }
        /// <summary>
        /// 根据接收到的xml格式数据填模型实例
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="req"></param>
        /// <param name="doc"></param>
        static void FillModelWithXml<T>(T req, XDocument doc) where T : Requests.Request, new()
        {
            req = req ?? new T();
            var root = doc.Root;
            var props = req.GetType().GetProperties();
            foreach (var p in props)
            {
                var pname = p.Name;
                var el = root.Element(pname);
                if (el != null)
                {
                    var v = el.Value;
                    switch (p.PropertyType.Name)
                    {
                        case "DateTime":
                            p.SetValue(req, ConvertToDateTime(v), null);
                            break;
                        case "Boolean":
                            if (pname == "FuncFlag")
                                p.SetValue(req, v == "1", null);
                            else
                                goto default;
                            break;
                        case "Int64":
                            p.SetValue(req, long.Parse(v), null);
                            break;
                        case "MsgType":
                            p.SetValue(req, GetMsgType(v), null);
                            break;
                        case "EventType":
                            p.SetValue(req, GetEventType(v), null);
                            break;
                        case "Double":
                            p.SetValue(req, double.Parse(v), null);
                            break;
                        case "Int32":
                            p.SetValue(req, int.Parse(v), null);
                            break;
                        default:
                            p.SetValue(req, v, null);
                            break;
                    }
                }
            }
        }
    }
}
